package vn.ugame.module;

import com.google.code.regexp.NamedMatcher;
import com.google.code.regexp.NamedPattern;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public abstract class AbstractModule implements Module {

    private ModuleConfig config;
    private HttpServletRequest request;
    private HttpServletResponse response;
    private ServletContext context;
    private String queryString;
    private String urlPatternMapping;

    public void execute(HttpServletRequest request, HttpServletResponse response, ServletContext context) throws ServletException, IOException {
        this.request = request;
        this.response = response;
        this.context = context;

        String path;

        path = (String) request.getAttribute("path");
        queryString = path.substring(config.getName().length() + 1);
        if (queryString.startsWith("/")) {
            queryString = queryString.substring(1);
        }

        Method action = findActionMethod(queryString);

        if (action == null) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        Object[] parameters = createActionParameters(action, urlPatternMapping, queryString);

        try {
            action.invoke(this, parameters);
        } catch (IllegalAccessException ex) {
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (IllegalArgumentException ex) {
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (InvocationTargetException ex) {
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }

    private Method findActionMethod(String queryString) {
        Method[] methods = this.getClass().getDeclaredMethods();
        for (Method method : methods) {
            ActionConfig action = method.getAnnotation(ActionConfig.class);
            if (action != null) {
                String[] urlPatterns = action.urlPattern();

                for (String urlPattern : urlPatterns) {
                    NamedPattern pattern = NamedPattern.compile(urlPattern);
                    NamedMatcher matcher = pattern.matcher(queryString);

                    if (matcher.matches()) {
                        this.urlPatternMapping = urlPattern;
                        return method;
                    }
                }
            }
        }
        return null;
    }

    private Object[] createActionParameters(Method action, String urlPattern, String queryString) {

        NamedPattern pattern = NamedPattern.compile(urlPattern);
        NamedMatcher matcher = pattern.matcher(queryString);

        Class[] parameterTypes = action.getParameterTypes();
        Object[] parameters = new Object[parameterTypes.length];

        for (int i = 0; i < parameterTypes.length; i++) {
            String parameterName = getParameterName(action, i);
            Class parameterType = parameterTypes[i];

            if (parameterType.isPrimitive() || parameterType.getCanonicalName().equals("java.lang.String")) {
                try {
                    matcher.find(0);
                    String groupValue = matcher.group(parameterName);
                    if (parameterType.isPrimitive()) {
                        parameters[i] = convertStringToPrimitive(groupValue, parameterType);
                    } else {
                        parameters[i] = groupValue;
                    }
                } catch (Exception ex) {
                    parameters[i] = getDefaultValue(parameterType);
                }
            } else {
                parameters[i] = null;
            }
        }

        return parameters;
    }

    private String getParameterName(Method method, int i) {
        String parameterName = null;
        for (Annotation anotation : method.getParameterAnnotations()[i]) {
            if (anotation instanceof Param) {
                parameterName = ((Param) anotation).value();
            }
        }
        return parameterName;
    }

    private Object getDefaultValue(Class type) {
        if (type.getCanonicalName().equals("boolean")) {
            return false;
        } else if (type.getCanonicalName().equals("int")) {
            return 0;
        } else if (type.getCanonicalName().equals("long")) {
            return 0;
        } else if (type.getCanonicalName().equals("double")) {
            return 0.0;
        } else if (type.getCanonicalName().equals("float")) {
            return 0.0;
        } else if (type.getCanonicalName().equals("java.lang.String")) {
            return null;
        } else if (type.getCanonicalName().equals("short")) {
            return 0;
        }
        return null;
    }

    public Object convertStringToPrimitive(String value, Class<?> destClass) throws InvocationTargetException {

        Map<Class, Class> primitiveMap = new HashMap<Class, Class>();

        primitiveMap.put(boolean.class, Boolean.class);
        primitiveMap.put(byte.class, Byte.class);
        primitiveMap.put(char.class, Character.class);
        primitiveMap.put(short.class, Short.class);
        primitiveMap.put(int.class, Integer.class);
        primitiveMap.put(long.class, Long.class);
        primitiveMap.put(float.class, Float.class);
        primitiveMap.put(double.class, Double.class);

        if (destClass.isPrimitive()) {
            destClass = primitiveMap.get(destClass);
        } else {
            return null;
        }

        try {
            Method m = destClass.getMethod("valueOf", String.class);
            int mods = m.getModifiers();
            if (Modifier.isStatic(mods) && Modifier.isPublic(mods)) {
                return m.invoke(null, value);
            }
        } catch (NoSuchMethodException e) {
            if (destClass == Character.class) {
                return Character.valueOf(value.charAt(0));
            }
        } catch (IllegalAccessException e) {
        }
        return null;
    }

    public ModuleConfig getConfig() {
        return config;
    }

    public void init(ModuleConfig config) {
        this.config = config;
    }

    public HttpServletRequest getRequest() {
        return request;
    }

    public HttpServletResponse getResponse() {
        return response;
    }

    public String getQueryString() {
        return queryString;
    }

    public String getUrlPatternMapping() {
        return urlPatternMapping;
    }

    public ServletContext getContext() {
        return context;
    }
}
